﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.DL.Data.Generic.Properties;

#endregion


namespace ScrumTable.DL.Data.Generic.Cache
{
    /// <summary>
    /// This class contains a simple Identity store which index the 
    /// identities by unique id and by id.
    /// </summary>
    public class CacheIdentityStore : IEnumerable<ICacheIdentity>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly IDictionary<string, CacheIdentityCategoryStore> _identities = new Dictionary<string, CacheIdentityCategoryStore>();

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the element in the given category (type) and
        /// with the given id (key).
        /// </summary>
        public ICacheIdentity this[string category, int key]
        {
            get { return GetCategoryStore(category).IdIndex[key]; }
        }

        /// <summary>
        /// Gets the element in the given category (type) and
        /// with the given unique id (key).
        /// </summary>
        public ICacheIdentity this[string category, string key]
        {
            get { return _identities[category].UniqueIdIndex[key]; }
            internal set
            {
                PreCondition.AssertNotNull(key, "key");

                CacheIdentityCategoryStore categoryStore = GetCategoryStore(category);
                categoryStore.UniqueIdIndex[key] = value;

                if (value.Id != CacheIdentity.DefaultId)
                {
                    categoryStore.IdIndex[value.Id] = value;
                }
            }
        }

        /// <summary>
        /// Gets the number of total elements in the current store.
        /// </summary>
        public int Count
        {
            get { return (from storeKeyPair in _identities select storeKeyPair.Value.UniqueIdIndex.Count).Sum(); }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="CacheIdentityStore"/> class.
        /// </summary>
        internal CacheIdentityStore()
        {
        }

        private CacheIdentityStore(IDictionary<string, CacheIdentityCategoryStore> identities)
        {
            PreCondition.AssertNotNull(identities, "identities");

            _identities = identities;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new writeable instance of the current object. This means
        /// that all public Setter procedure store values back into the writeable
        /// instance. The implementation follows the Copy-On-Write pattern.
        /// </summary>
        /// <returns>Returns the new writeable instance.</returns>
        public CacheIdentityStore ToWriteable()
        {
            return new CacheIdentityStoreWriteable(_identities.CopyToDictionary());
        }

        /// <summary>
        ///  <see cref="IEnumerable{T}.GetEnumerator"/>
        /// </summary>
        public IEnumerator<ICacheIdentity> GetEnumerator()
        {
            return (from identity in _identities
                    from index in identity.Value.IdIndex
                    select index.Value).GetEnumerator();
        }

        /// <summary>
        ///  <see cref="IEnumerable{T}.GetEnumerator"/>
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Gets the element with the given type and unique id.
        /// </summary>
        /// <param name="key">Specifies the id of the element to retrieve.</param>
        public ICacheIdentity GetByType<T>(int key)
        {
            return this[CacheElementFactory.GetCategoryByType(typeof(T)), key];
        }

        /// <summary>
        /// Gets all identities below teh given category (element type).
        /// </summary>
        public IEnumerable<ICacheIdentity> GetAllByType<T>()
        {
            return GetCategoryStore(CacheElementFactory.GetCategoryByType(typeof(T))).UniqueIdIndex.Values;
        }

        /// <summary>
        /// Gets the element with the given type and unique id.
        /// </summary>
        /// <param name="key">Specifies the unique id of the element to retrieve.</param>
        public ICacheIdentity GetByType<T>(string key)
        {
            return this[CacheElementFactory.GetCategoryByType(typeof(T)), key];
        }

        /// <summary>
        /// Sets the element with the given type and unique id.
        /// </summary>
        /// <param name="key">Specifies the unique id of the element to set.</param>
        /// <param name="value">Specifies the identity to set.</param>
        public virtual void SetByType<T>(string key, ICacheIdentity value)
        {
            throw new InvalidOperationException(Resources.ExcCacheIdStoreCouldNotWrite);
        }

        /// <summary>
        /// Checks whether the current store contains an element associated
        /// with the given category and id.
        /// </summary>
        public bool ContainsKey(string category, int key)
        {
            if (_identities.ContainsKey(category))
            {
                return _identities[category].IdIndex.ContainsKey(key);
            }
            return false;
        }

        /// <summary>
        /// Checks whether the current store contains an element associated
        /// with the given category type and id.
        /// </summary>
        public bool ContainsKey<T>(int key)
            where T : ICacheElement
        {
            return ContainsKey(
                CacheElementFactory.GetCategoryByType(typeof(T)),
                key);
        }

        /// <summary>
        /// Checks whether the current store contains an element associated
        /// with the given category and unique id.
        /// </summary>
        public bool ContainsKey(string category, string key)
        {
            if (_identities.ContainsKey(category))
            {
                return _identities[category].UniqueIdIndex.ContainsKey(key);
            }
            return false;
        }

        /// <summary>
        /// Checks whether the current store contains an element associated
        /// with the given category type and unique id.
        /// </summary>
        public bool ContainsKey<T>(string key)
            where T : ICacheElement
        {
            return ContainsKey(
                CacheElementFactory.GetCategoryByType(typeof (T)),
                key);
        }

        private CacheIdentityCategoryStore GetCategoryStore(string category)
        {
            if (!_identities.ContainsKey(category))
            {
                _identities[category] = new CacheIdentityCategoryStore();
            }
            return _identities[category];
        }

        #endregion

        #region Events

        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion


        
        //--------------------------------------------------------------------
        // Nested Classes
        //--------------------------------------------------------------------

        /// <summary>
        /// Contains a category specific store for a list of identities.
        /// </summary>
        private class CacheIdentityCategoryStore
        {
            internal IDictionary<int, ICacheIdentity> IdIndex { get; private set; }
            internal IDictionary<string, ICacheIdentity> UniqueIdIndex { get; private set; }

            internal CacheIdentityCategoryStore()
            {
                IdIndex = new Dictionary<int, ICacheIdentity>();
                UniqueIdIndex = new Dictionary<string, ICacheIdentity>();                
            }
        }


        /// <summary>
        /// Provides a writeable public instance of the current CacheIdentityStore.
        /// </summary>
        private class CacheIdentityStoreWriteable : CacheIdentityStore
        {
            #region Declarations
            //--------------------------------------------------------------------
            // Declarations
            //--------------------------------------------------------------------

            #endregion

            #region Constructors / Destructor
            //--------------------------------------------------------------------
            // Constructors / Destructor
            //--------------------------------------------------------------------

            internal CacheIdentityStoreWriteable(IDictionary<string, CacheIdentityCategoryStore> identities)
                : base(identities)
            {
            }

            #endregion

            #region Methods
            //--------------------------------------------------------------------
            // Methods
            //--------------------------------------------------------------------

            /// <summary>
            /// Sets the element with the given type and unique id.
            /// </summary>
            /// <param name="key">Specifies the unique id of the element to set.</param>
            /// <param name="value">Specifies the identity to set.</param>
            public override void SetByType<T>(string key, ICacheIdentity value)
            {
                this[CacheElementFactory.GetCategoryByType(typeof(T)), key] = value;
            }

            #endregion
        }
    }
}